/*
 * libslirp glue
 *
 * Copyright (c) 2004-2008 Fabrice Bellard
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "qemu-common.h"
#include "qemu/timer.h"
#include "sysemu/char.h"
#include "slirp.h"
#include "hw/hw.h"

/* host loopback address */
struct in_addr loopback_addr;
/* host loopback network mask */
unsigned long loopback_mask;

/* emulated hosts use the MAC addr 52:55:IP:IP:IP:IP */
static const uint8_t special_ethaddr[ETH_ALEN] = {
    0x52, 0x55, 0x00, 0x00, 0x00, 0x00
};

u_int curtime;

static QTAILQ_HEAD(slirp_instances, Slirp) slirp_instances =
    QTAILQ_HEAD_INITIALIZER(slirp_instances);

static struct in_addr dns_addr;
static u_int dns_addr_time;

#define TIMEOUT_FAST 2  /* milliseconds */
#define TIMEOUT_SLOW 499  /* milliseconds */
/* for the aging of certain requests like DNS */
#define TIMEOUT_DEFAULT 1000  /* milliseconds */



#define CONN_CANFSEND(so) (((so)->so_state & (SS_FCANTSENDMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED)
#define CONN_CANFRCV(so) (((so)->so_state & (SS_FCANTRCVMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED)

static void slirp_update_timeout(uint32_t *timeout)
{
    Slirp *slirp;
    uint32_t t;

    if (*timeout <= TIMEOUT_FAST) {
        return;
    }

    t = MIN(1000, *timeout);

    /* If we have tcp timeout with slirp, then we will fill @timeout with
     * more precise value.
     */
    QTAILQ_FOREACH(slirp, &slirp_instances, entry) {
        if (slirp->time_fasttimo) {
            *timeout = TIMEOUT_FAST;
            return;
        }
        if (slirp->do_slowtimo) {
            t = MIN(TIMEOUT_SLOW, t);
        }
    }
    *timeout = t;
}

void slirp_pollfds_fill(GArray *pollfds, uint32_t *timeout)
{
    Slirp *slirp;
    struct socket *so, *so_next;

    if (QTAILQ_EMPTY(&slirp_instances)) {
        return;
    }

    /*
     * First, TCP sockets
     */

    QTAILQ_FOREACH(slirp, &slirp_instances, entry) {
        /*
         * *_slowtimo needs calling if there are IP fragments
         * in the fragment queue, or there are TCP connections active
         */
        slirp->do_slowtimo = ((slirp->tcb.so_next != &slirp->tcb) ||
                (&slirp->ipq.ip_link != slirp->ipq.ip_link.next));

        for (so = slirp->tcb.so_next; so != &slirp->tcb;
                so = so_next) {
            int events = 0;

            so_next = so->so_next;

            so->pollfds_idx = -1;

            /*
             * See if we need a tcp_fasttimo
             */
            if (slirp->time_fasttimo == 0 &&
                so->so_tcpcb->t_flags & TF_DELACK) {
                slirp->time_fasttimo = curtime; /* Flag when want a fasttimo */
            }

            /*
             * NOFDREF can include still connecting to local-host,
             * newly socreated() sockets etc. Don't want to select these.
             */
            if (so->so_state & SS_NOFDREF || so->s == -1) {
                continue;
            }

            /*
             * Set for reading sockets which are accepting
             */
            if (so->so_state & SS_FACCEPTCONN) {
                GPollFD pfd = {
                    .fd = so->s,
                    .events = G_IO_IN | G_IO_HUP | G_IO_ERR,
                };
                so->pollfds_idx = pollfds->len;
                g_array_append_val(pollfds, pfd);
                continue;
            }

            /*
             * Set for writing sockets which are connecting
             */
            if (so->so_state & SS_ISFCONNECTING) {
                GPollFD pfd = {
                    .fd = so->s,
                    .events = G_IO_OUT | G_IO_ERR,
                };
                so->pollfds_idx = pollfds->len;
                g_array_append_val(pollfds, pfd);
                continue;
            }

            /*
             * Set for writing if we are connected, can send more, and
             * we have something to send
             */
            if (CONN_CANFSEND(so) && so->so_rcv.sb_cc) {
                events |= G_IO_OUT | G_IO_ERR;
            }

            /*
             * Set for reading (and urgent data) if we are connected, can
             * receive more, and we have room for it XXX /2 ?
             */
            if (CONN_CANFRCV(so) &&
                (so->so_snd.sb_cc < (so->so_snd.sb_datalen/2))) {
                events |= G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI;
            }

            if (events) {
                GPollFD pfd = {
                    .fd = so->s,
                    .events = events,
                };
                so->pollfds_idx = pollfds->len;
                g_array_append_val(pollfds, pfd);
            }
        }

        /*
         * UDP sockets
         */
        for (so = slirp->udb.so_next; so != &slirp->udb;
                so = so_next) {
            so_next = so->so_next;

            so->pollfds_idx = -1;

            /*
             * See if it's timed out
             */
            if (so->so_expire) {
                if (so->so_expire <= curtime) {
                    udp_detach(so);
                    continue;
                } else {
                    slirp->do_slowtimo = true; /* Let socket expire */
                }
            }

            /*
             * When UDP packets are received from over the
             * link, they're sendto()'d straight away, so
             * no need for setting for writing
             * Limit the number of packets queued by this session
             * to 4.  Note that even though we try and limit this
             * to 4 packets, the session could have more queued
             * if the packets needed to be fragmented
             * (XXX <= 4 ?)
             */
            if ((so->so_state & SS_ISFCONNECTED) && so->so_queued <= 4) {
                GPollFD pfd = {
                    .fd = so->s,
                    .events = G_IO_IN | G_IO_HUP | G_IO_ERR,
                };
                so->pollfds_idx = pollfds->len;
                g_array_append_val(pollfds, pfd);
            }
        }

        /*
         * ICMP sockets
         */
        for (so = slirp->icmp.so_next; so != &slirp->icmp;
                so = so_next) {
            so_next = so->so_next;

            so->pollfds_idx = -1;

            /*
             * See if it's timed out
             */
            //if (so->so_expire) {
            //    if (so->so_expire <= curtime) {
            //        icmp_detach(so);
            //        continue;
            //    } else {
            //        slirp->do_slowtimo = true; /* Let socket expire */
            //    }
            //}

            if (so->so_state & SS_ISFCONNECTED) {
                GPollFD pfd = {
                    .fd = so->s,
                    .events = G_IO_IN | G_IO_HUP | G_IO_ERR,
                };
                so->pollfds_idx = pollfds->len;
                g_array_append_val(pollfds, pfd);
            }
        }
    }
    slirp_update_timeout(timeout);
}

void slirp_pollfds_poll(GArray *pollfds, int select_error)
{
    Slirp *slirp;
    struct socket *so, *so_next;
    int ret;

    if (QTAILQ_EMPTY(&slirp_instances)) {
        return;
    }

}

